"use strict";

function _instanceof(left, right) {
  if (
    right != null &&
    typeof Symbol !== "undefined" &&
    right[Symbol.hasInstance]
  ) {
    return !!right[Symbol.hasInstance](left);
  } else {
    return left instanceof right;
  }
}

function _typeof(obj) {
  "@babel/helpers - typeof";
  return (
    (_typeof =
      "function" == typeof Symbol && "symbol" == typeof Symbol.iterator
        ? function(obj) {
            return typeof obj;
          }
        : function(obj) {
            return obj &&
              "function" == typeof Symbol &&
              obj.constructor === Symbol &&
              obj !== Symbol.prototype
              ? "symbol"
              : typeof obj;
          }),
    _typeof(obj)
  );
}

function _classCallCheck(instance, Constructor) {
  if (!_instanceof(instance, Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

function _defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    var descriptor = props[i];
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor);
  }
}

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  Object.defineProperty(Constructor, "prototype", { writable: false });
  return Constructor;
}

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  } else {
    obj[key] = value;
  }
  return obj;
}

var QWebChannelMessageTypes = {
  signal: 1,
  propertyUpdate: 2,
  init: 3,
  idle: 4,
  debug: 5,
  invokeMethod: 6,
  connectToSignal: 7,
  disconnectFromSignal: 8,
  setProperty: 9,
  response: 10
};

var QWebChannel = /*#__PURE__*/ (function() {
  function QWebChannel(transport, initCallback) {
    var _this = this;

    _classCallCheck(this, QWebChannel);

    _defineProperty(this, "transport", void 0);

    _defineProperty(this, "initCallback", void 0);

    _defineProperty(this, "execCallbacks", {});

    _defineProperty(this, "execId", 0);

    _defineProperty(this, "objects", {});

    if (
      _typeof(transport) !== "object" ||
      typeof transport.send !== "function"
    ) {
      console.error(
        "The QWebChannel expects a transport object with a send function and onmessage callback property." +
          " Given is: transport: " +
          _typeof(transport) +
          ", transport.send: " +
          _typeof(transport.send)
      );
      return;
    }

    this.transport = transport;
    this.initCallback = initCallback;

    this.transport.onmessage = function(message) {
      var data = message.data;

      if (typeof data === "string") {
        data = JSON.parse(data);
      }

      switch (data.type) {
        case QWebChannelMessageTypes.signal:
          _this.handleSignal(data);

          break;

        case QWebChannelMessageTypes.response:
          _this.handleResponse(data);

          break;

        case QWebChannelMessageTypes.propertyUpdate:
          _this.handlePropertyUpdate(data);

          break;

        default:
          console.error("invalid message received:", message.data);
          break;
      }
    };

    this.exec(
      {
        type: QWebChannelMessageTypes.init
      },
      function(data) {
        for (
          var _i = 0, _Object$keys = Object.keys(data);
          _i < _Object$keys.length;
          _i++
        ) {
          var objectName = _Object$keys[_i];
          new QObject(objectName, data[objectName], _this);
        } // now unwrap properties, which might reference other registered objects

        for (
          var _i2 = 0, _Object$keys2 = Object.keys(_this.objects);
          _i2 < _Object$keys2.length;
          _i2++
        ) {
          var objectName = _Object$keys2[_i2];

          _this.objects[objectName].unwrapProperties();
        }

        if (_this.initCallback) {
          _this.initCallback(_this);
        }

        _this.exec({
          type: QWebChannelMessageTypes.idle
        });
      }
    );
  }

  _createClass(QWebChannel, [
    {
      key: "send",
      value: function send(data) {
        if (typeof data !== "string") {
          data = JSON.stringify(data);
        }

        this.transport.send(data);
      }
    },
    {
      key: "exec",
      value: function exec(data, callback) {
        if (!callback) {
          // if no callback is given, send directly
          this.send(data);
          return;
        }

        if (this.execId === Number.MAX_VALUE) {
          // wrap
          this.execId = Number.MIN_VALUE;
        } // eslint-disable-next-line no-prototype-builtins

        if (data.hasOwnProperty("id")) {
          console.error(
            "Cannot exec message with property id: " + JSON.stringify(data)
          );
          return;
        }

        data.id = this.execId++;
        this.execCallbacks[data.id] = callback;
        this.send(data);
      }
    },
    {
      key: "handleSignal",
      value: function handleSignal(message) {
        var object = this.objects[message.object];

        if (object) {
          object.signalEmitted(message.signal, message.args);
        } else {
          console.warn(
            "Unhandled signal: " + message.object + "::" + message.signal
          );
        }
      }
    },
    {
      key: "handleResponse",
      value: function handleResponse(message) {
        // eslint-disable-next-line no-prototype-builtins
        if (!message.hasOwnProperty("id")) {
          console.error(
            "Invalid response message received: ",
            JSON.stringify(message)
          );
          return;
        }

        this.execCallbacks[message.id](message.data);
        delete this.execCallbacks[message.id];
      }
    },
    {
      key: "handlePropertyUpdate",
      value: function handlePropertyUpdate(message) {
        for (
          var _i3 = 0, _Object$keys3 = Object.keys(message);
          _i3 < _Object$keys3.length;
          _i3++
        ) {
          var i = _Object$keys3[_i3];
          var data = message.data[i];
          var object = this.objects[data.object];

          if (object) {
            object.propertyUpdate(data.signals, data.properties);
          } else {
            console.warn(
              "Unhandled property update: " + data.object + "::" + data.signal
            );
          }
        }

        this.exec({
          type: QWebChannelMessageTypes.idle
        });
      }
    },
    {
      key: "debug",
      value: function debug(message) {
        this.send({
          type: QWebChannelMessageTypes.debug,
          data: message
        });
      }
    }
  ]);

  return QWebChannel;
})();

function QObject(name, data, webChannel) {
  this.__id__ = name;
  webChannel.objects[name] = this; // List of callbacks that get invoked upon signal emission

  this.__objectSignals__ = {}; // Cache of all properties, updated when a notify signal is emitted

  this.__propertyCache__ = {};
  var object = this; // ----------------------------------------------------------------------

  this.unwrapQObject = function(response) {
    if (_instanceof(response, Array)) {
      // support list of objects
      var ret = new Array(response.length);

      for (var i = 0; i < response.length; ++i) {
        ret[i] = object.unwrapQObject(response[i]);
      }

      return ret;
    }

    if (!response || !response["__QObject*__"] || response.id === undefined) {
      return response;
    }

    var objectId = response.id;

    if (webChannel.objects[objectId]) {
      return webChannel.objects[objectId];
    }

    if (!response.data) {
      console.error(
        "Cannot unwrap unknown QObject " + objectId + " without data."
      );
      return;
    }

    var qObject = new QObject(objectId, response.data, webChannel);
    qObject.destroyed.connect(function() {
      if (webChannel.objects[objectId] === qObject) {
        delete webChannel.objects[objectId]; // reset the now deleted QObject to an empty {} object
        // just assigning {} though would not have the desired effect, but the
        // below also ensures all external references will see the empty map
        // NOTE: this detour is necessary to workaround QTBUG-40021

        var propertyNames = [];

        for (
          var _i4 = 0, _Object$keys4 = Object.keys(qObject);
          _i4 < _Object$keys4.length;
          _i4++
        ) {
          var propertyName = _Object$keys4[_i4];
          propertyNames.push(propertyName);
        }

        for (
          var _i5 = 0, _Object$keys5 = Object.keys(propertyNames);
          _i5 < _Object$keys5.length;
          _i5++
        ) {
          var idx = _Object$keys5[_i5];
          delete qObject[propertyNames[idx]];
        }
      }
    }); // here we are already initialized, and thus must directly unwrap the properties

    qObject.unwrapProperties();
    return qObject;
  };

  this.unwrapProperties = function() {
    for (
      var _i6 = 0, _Object$keys6 = Object.keys(object.__propertyCache__);
      _i6 < _Object$keys6.length;
      _i6++
    ) {
      var propertyIdx = _Object$keys6[_i6];
      object.__propertyCache__[propertyIdx] = object.unwrapQObject(
        object.__propertyCache__[propertyIdx]
      );
    }
  };

  function addSignal(signalData, isPropertyNotifySignal) {
    var signalName = signalData[0];
    var signalIndex = signalData[1];
    object[signalName] = {
      connect: function connect(callback) {
        if (typeof callback !== "function") {
          console.error(
            "Bad callback given to connect to signal " + signalName
          );
          return;
        }

        object.__objectSignals__[signalIndex] =
          object.__objectSignals__[signalIndex] || [];

        object.__objectSignals__[signalIndex].push(callback);

        if (!isPropertyNotifySignal && signalName !== "destroyed") {
          // only required for "pure" signals, handled separately for properties in propertyUpdate
          // also note that we always get notified about the destroyed signal
          webChannel.exec({
            type: QWebChannelMessageTypes.connectToSignal,
            object: object.__id__,
            signal: signalIndex
          });
        }
      },
      disconnect: function disconnect(callback) {
        if (typeof callback !== "function") {
          console.error(
            "Bad callback given to disconnect from signal " + signalName
          );
          return;
        }

        object.__objectSignals__[signalIndex] =
          object.__objectSignals__[signalIndex] || [];

        var idx = object.__objectSignals__[signalIndex].indexOf(callback);

        if (idx === -1) {
          console.error(
            "Cannot find connection of signal " +
              signalName +
              " to " +
              callback.name
          );
          return;
        }

        object.__objectSignals__[signalIndex].splice(idx, 1);

        if (
          !isPropertyNotifySignal &&
          object.__objectSignals__[signalIndex].length === 0
        ) {
          // only required for "pure" signals, handled separately for properties in propertyUpdate
          webChannel.exec({
            type: QWebChannelMessageTypes.disconnectFromSignal,
            object: object.__id__,
            signal: signalIndex
          });
        }
      }
    };
  }
  /**
   * Invokes all callbacks for the given signalname. Also works for property notify callbacks.
   */

  function invokeSignalCallbacks(signalName, signalArgs) {
    var connections = object.__objectSignals__[signalName];

    if (connections) {
      connections.forEach(function(callback) {
        callback.apply(callback, signalArgs);
      });
    }
  }

  this.propertyUpdate = function(signals, propertyMap) {
    // update property cache
    for (
      var _i7 = 0, _Object$keys7 = Object.keys(propertyMap);
      _i7 < _Object$keys7.length;
      _i7++
    ) {
      var propertyIndex = _Object$keys7[_i7];
      var propertyValue = propertyMap[propertyIndex];
      object.__propertyCache__[propertyIndex] = propertyValue;
    }

    for (
      var _i8 = 0, _Object$keys8 = Object.keys(signals);
      _i8 < _Object$keys8.length;
      _i8++
    ) {
      var signalName = _Object$keys8[_i8];
      // Invoke all callbacks, as signalEmitted() does not. This ensures the
      // property cache is updated before the callbacks are invoked.
      invokeSignalCallbacks(signalName, signals[signalName]);
    }
  };

  this.signalEmitted = function(signalName, signalArgs) {
    invokeSignalCallbacks(signalName, this.unwrapQObject(signalArgs));
  };

  function addMethod(methodData) {
    var methodName = methodData[0];
    var methodIdx = methodData[1];

    object[methodName] = function() {
      var args = [];
      var callback;

      for (var i = 0; i < arguments.length; ++i) {
        var argument = arguments[i];

        if (typeof argument === "function") {
          callback = argument;
        } else if (
          _instanceof(argument, QObject) &&
          webChannel.objects[argument.__id__] !== undefined
        ) {
          args.push({
            id: argument.__id__
          });
        } else {
          args.push(argument);
        }
      }

      webChannel.exec(
        {
          type: QWebChannelMessageTypes.invokeMethod,
          object: object.__id__,
          method: methodIdx,
          args: args
        },
        function(response) {
          if (response !== undefined) {
            var result = object.unwrapQObject(response);

            if (callback) {
              callback(result);
            }
          }
        }
      );
    };
  }

  function bindGetterSetter(propertyInfo) {
    var propertyIndex = propertyInfo[0];
    var propertyName = propertyInfo[1];
    var notifySignalData = propertyInfo[2]; // initialize property cache with current value
    // NOTE: if this is an object, it is not directly unwrapped as it might
    // reference other QObject that we do not know yet

    object.__propertyCache__[propertyIndex] = propertyInfo[3];

    if (notifySignalData) {
      if (notifySignalData[0] === 1) {
        // signal name is optimized away, reconstruct the actual name
        notifySignalData[0] = propertyName + "Changed";
      }

      addSignal(notifySignalData, true);
    }

    Object.defineProperty(object, propertyName, {
      configurable: true,
      get: function get() {
        var propertyValue = object.__propertyCache__[propertyIndex];

        if (propertyValue === undefined) {
          // This shouldn't happen
          console.warn(
            'Undefined value in property cache for property "' +
              propertyName +
              '" in object ' +
              object.__id__
          );
        }

        return propertyValue;
      },
      set: function set(value) {
        if (value === undefined) {
          console.warn(
            "Property setter for " +
              propertyName +
              " called with undefined value!"
          );
          return;
        }

        object.__propertyCache__[propertyIndex] = value;
        var valueToSend = value;

        if (
          _instanceof(valueToSend, QObject) &&
          webChannel.objects[valueToSend.__id__] !== undefined
        ) {
          valueToSend = {
            id: valueToSend.__id__
          };
        }

        webChannel.exec({
          type: QWebChannelMessageTypes.setProperty,
          object: object.__id__,
          property: propertyIndex,
          value: valueToSend
        });
      }
    });
  } // ----------------------------------------------------------------------

  data.methods.forEach(addMethod);
  data.properties.forEach(bindGetterSetter);
  data.signals.forEach(function(signal) {
    addSignal(signal, false);
  }); // of Object.keys(data.enums)

  for (var name in data.enums) {
    object[name] = data.enums[name];
  }
}
